using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

using CommonTypes.Math;
using CommonTypes.Math.Geometry3D;
using CommonTypes;

namespace BuildingBasicDomain
{
    [Serializable ]
    public class Area : IArea  ,IContainer <IPoint>,IComparer <IPoint>
    {
        # region " private Decleration "

        Polygon  polygon;
        public Polygon Polygon
        {
            set
            {
                polygon = (Polygon)value;
            }

            get
            {
                if (polygon == null)
                    polygon = new Polygon();
                return polygon;
            }
        }

        Plane plane;
        public Plane Plane
        {
            set
            {
                plane = value;
            }
            get
            {
                //lazy initialization. when there are at least 3 points call plane constructor .
                if (Polygon.Count >= 3)
                {
                    Vector3F p1 = new Vector3F((float)Polygon[0].X, (float)Polygon[0].Y, (float)Polygon[0].Z);
                    Vector3F p2 = new Vector3F((float)Polygon[1].X, (float)Polygon[1].Y, (float)Polygon[1].Z);
                    Vector3F p3 = new Vector3F((float)Polygon[2].X, (float)Polygon[2].Y, (float)Polygon[2].Z);

                    plane = new Plane(p1, p2, p3);
                }
                return plane;
            }
        }

        # endregion

        # region " IPlane Implementation "

        public Vector3F normal
        {
            get
            {
                return this.Plane.Normal;
            }
            set
            {
                plane.Normal = value;
            }
        }
        public float Constant
        {
            get
            {
                return plane.Constant;
            }
            set
            {
                plane.Constant = value;
            }
        }

        public bool containsLine(ILine line)
        {
            throw new NotImplementedException("Not implemented");
        }
        public bool isParallel(ILine line)
        {
            if (Vector3D.DotProduct(line.Direction, (Vector3D)normal) == 0)
                return true;
            else
                return false;
        }
        public IPoint intersectsPlane(ILine line)
        {
            Vector3F intersection = Plane.GetIntersection((Vector3F)line.SNode.V, (Vector3F)line.ENode.V);
            IPoint point = new Point(intersection.X, intersection.Y, intersection.Z);
            return point;
        }
        public double distancePoint(IPoint point)
        {
            return Plane.GetDistanceToPlane((Vector3F)point.V);
        }
        public double distanceLine(ILine line)
        {
            if (isParallel(line))
            {
                return distancePoint(line.SNode);
            }
            else return -1;
        }
        public ILine projectedLine(ILine line)
        {
            ILine newLine = new Line();
            newLine.SNode = this.projectedPoint(line.SNode);
            newLine.ENode = this.projectedPoint(line.ENode);
            return newLine;

        }
        public IPoint projectedPoint(IPoint point)
        {
            try
            {
                double distance = -Plane.GetDistanceToPlane((Vector3F)point.V);

                Vector3F point2 = ((Vector3F)point.V) + ((float)(distance * 2)) * plane.Normal;
                if (distance < 0.01) return (Point)point;
                Vector3F intersection = plane.GetIntersection(point2, (Vector3F)point.V);

                return (Point)(Vector3D)intersection;

            }
            catch { }
            return null; 

        }

        # endregion

        # region " IContainer Implementation "
         
        public virtual bool Add(IPoint point)
        {
            if (!Polygon.Contains(point))
            {
                AddSorted( point);
                return true;
            }
            return false;

        }

        private void AddSorted(IPoint point)
        {
            ILine edge ;
            for (int i = 0; i < Polygon.Count - 1; i++)
            {
                edge = new Line(Polygon[i], Polygon[i + 1]);
                if (edge.ContainsPointOpen(point))
                {
                    Polygon.Insert(i + 1, point);
                    return;
                }
            }
            Polygon.Add(point);
        }
        public virtual bool Remove(IPoint point)
        {
            if (Polygon.Contains(point))
            {
                Polygon.Remove(point);
                return true;
            }
            return false;
        }
        public virtual bool Contains(IPoint point)
        {
            return Polygon.Contains(point);
        }
        public virtual int Count
        {
            get
            {
                return Polygon.Count;
            }
        }
        public virtual IPoint this[int i]
        {
            get
            {
                if (i >= 0 && i < Count)
                    return Polygon[i];
                return null;
            }
            set
            {
                if (i >= 0 && i < Count)
                    Polygon[i] = value;
            }
        }

        #region IEnumerable<IPoint> Members

        public IEnumerator GetEnumerator()
        {
            return polygon.GetEnumerator();
        }

        #endregion
        # endregion

        # region " ISelectable , ICountable implementation"
        private bool isSelected;
        public bool IsSelected
        {
            get { return isSelected; }
            set { this.isSelected = value; }
        }
        private int id;

        public int Id
        {
            get { return id; }
            set { id = value; }
        }

        #endregion

        # region " General Decleration "

        public double GetArea()
        {
            double area = 0.0;

            for (int i = 0; i < Count - 1; i++)
            {
                area += Polygon[i].X * Polygon[i + 1].Y - Polygon[i + 1].X * Polygon[i].Y;
            }

            area /= 2;
            return area;
        }

        /// <summary>
        /// Method GetPerimeter
        /// </summary>
        public double GetPerimeter()
        {
            return Polygon.Perimeter;
        }

        /// <summary>
        /// Method IsVertical
        /// </summary>
        /// <returns>A bool</returns>
        public bool isVertical()
        {
            if (Vector3D.DotProduct((Vector3D)normal, new Vector3D(0, 1, 0)) == 0)
                return true;
            return false;
        }

        public IPoint GetMiddle()
        {
            
            double[] coords = new double[3];
            foreach (IPoint point in this)
            {
                coords[0] = coords[0] + point.X;
                coords[1] = coords[1] + point.Y;
                coords[2] = coords[2] + point.Z;
            }
            coords[0] = coords[0] / this.Count;
            coords[1] = coords[1] / this.Count;
            coords[2] = coords[2] / this.Count;
            return new Point(coords[0], coords[1], coords[2]);
        }

        /// <summary>
        /// Method IsHorizontal
        /// </summary>
        /// <returns>A bool</returns>
        public bool isHorizontal()
        {
            if (Vector3D.CrossProduct((Vector3D)this.Plane.Normal, new Vector3D(0, 1, 0)).GetLength() == 0)
                return true;
            return false;
        }

        public IPoint[] GetSelectionPerimeter()
        {
            IPoint[] selectionPerimeter = new IPoint[Polygon.Count];
            Vector n;
            Vector vb1;
            //Vector vb2;

            for (int i = 0; i < this.Polygon.Count; i++)
            {
                n = new Vector(Polygon[i].X, Polygon[i].Y, Polygon[i].Z);
                vb1 = AngleBisection(Polygon, i);
                // vb2 = AngleBisection(perimeter, i + 1);
                n = n + vb1;
                selectionPerimeter[i] = new Point  (n.X, n.Y, n.Z);

            }
            return selectionPerimeter;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Nodes"></param>
        /// <param name="i"></param>
        /// <returns></returns>
        private static Vector AngleBisection(IList<IPoint> Nodes, int i)
        {
// i dont like this

            Vector v1;
            Vector v0;
            Vector v2;
            int max = Nodes.Count - 1;
            if (i == 0)
                v1 = new Vector(Nodes[max ].X, Nodes[max ].Y, Nodes[max ].Z);
            else
                v1 = new Vector(Nodes[i - 1].X, Nodes[i - 1].Y, Nodes[i - 1].Z);

            v0 = new Vector(Nodes[i].X, Nodes[i].Y, Nodes[i].Z);

            if (i == max)
                v2 = new Vector(Nodes[0].X, Nodes[0].Y, Nodes[0].Z);
            else
                v2 = new Vector(Nodes[i + 1].X, Nodes[i + 1].Y, Nodes[i + 1].Z);

            v1 = v1 - v0;
            v2 = v2 - v0;
            v1.Normalize();
            v2.Normalize();


            v0 = v1 + v2;
            v0.Normalize();
            v0 = v0 * 100;
            return v0;
        }

        public Area[] destributeOnBeams(ILine[] lines)
        {
            if (lines == null) return null;
            Area[] areas = new Area[lines.Length - 2];

            Area area;
            //areas[0] = MakeArea(lines[0], lines[0], lines[1]);
            for (int i = 1; i < lines.Length - 1; i++)
            {
                area = MakeArea(lines[i - 1], lines[i], lines[i + 1]);
                areas[i - 1] = area;
            }
            //areas[lines.Length-1] = MakeArea(lines[lines.Length-2], 
            //    lines[lines.Length - 2], lines[lines.Length-1]);

            return areas;

        }

        private static Area MakeArea(ILine line1, ILine line2, ILine line3)
        {
            double x1 = (line2.SNode.X + line1.SNode.X) / 2;
            double y1 = (line2.SNode.Y);
            double z1 = (line2.SNode.Z + line1.SNode.Z) / 2;
            IPoint poin1 = new Point(x1, y1, z1);

            double x2 = (line2.ENode.X + line1.ENode.X) / 2;
            double y2 = (line2.ENode.Y);
            double z2 = (line2.ENode.Z + line1.ENode.Z) / 2;
            IPoint point2 = new Point(x2, y2, z2);

            double x3 = (line2.SNode.X + line3.SNode.X) / 2;
            double y3 = (line2.SNode.Y);
            double z3 = (line2.SNode.Z + line3.SNode.Z) / 2;
            IPoint point3 = new Point(x3, y3, z3);

            double x4 = (line2.ENode.X + line3.ENode.X) / 2;
            double y4 = (line2.ENode.Y);
            double z4 = (line2.ENode.Z + line3.ENode.Z) / 2;
            IPoint point4 = new Point(x4, y4, z4);

            Area area = new Area();

            area.Add(point3 );
            area.Add(point4 );
            area.Add(point2 );
            area.Add(poin1 );

            return area;
        }

        /*
        public NodeLine[] createSimpleLines(int divisions)
        {


            int lenght = this.Polygon.Count;
            if (lenght <= 0) return null;
            Node n1;
            Node n2;
            Node n3;
            if (this.polygon[0] is GridPoint)
            {
                n1 = (Node)(this.Polygon[0] as GridPoint);
                n2 = (Node)(this.Polygon[lenght - 1] as GridPoint);
                n3 = (Node)(this.Polygon[1] as GridPoint);
            }
            else
            {
                n1 = (Node)(this.Polygon[0]);
                n2 = (Node)(this.Polygon[lenght - 1]);
                n3 = (Node)(this.Polygon[1]);
            }



            Vector v1 = (Vector)n1;
            Vector v2 = (Vector)n2;
            Vector v3 = (Vector)n3;

            NodeLine[] nodelines = new NodeLine[divisions - 1];

            Node nc1;
            Node nc2;
            v3 = v3 - v1;

            double step;
            step = (1.0d / divisions);
            //v3.Normalize ();


            for (int j = 0; j < divisions - 1; j++)
            {
                v1 = v1 + v3 * (step);
                nc1 = new Node(v1.X, v1.Y, v1.Z);
                v2 = v2 + v3 * (step);
                nc2 = new Node(v2.X, v2.Y, v2.Z);
                NodeLine _line = new NodeLine(nc1, nc2);
                nodelines[j] = _line;
            }
            return nodelines;
        }
        */
        # endregion

        # region " IModifiable"
        public void Move(Vector v)
        {
            throw new NotImplementedException("Move is not implemented ");
        }
        # endregion

        #region IComparer<IPoint> Members

        public int Compare(IPoint x, IPoint y)
        {
            if (this[0].distancePoint(x) > this[0].distancePoint(y))
                return 1;
            else return -1;
        }

        #endregion

        #region IArea Members


        public bool containsPoint(IPoint point)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    }
}
